package zdream.pmfield.storage;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import zdream.pmfield.data.foe.Ability;
import zdream.pmfield.data.foe.FoeTemplate;
import zdream.pmfield.data.foe.MoveTemplate;

/**
 * @author Zdream
 * @date 2023-11-10
 * @since 0.0.1
 */
public class FoeTemplateStorage {
	public static final String RES_PATH_FOES_JSON = "foes.json";
	public static final String RES_PATH_MOVES_JSON = "moves.json";
	public static final String RES_PATH_STAGE_MOVES_JSON = "stage-moves.json";
	public static final String RES_PATH_ABILITIES_JSON = "abilities.json";
	public static final String RES_PATH_STAGE_ABILITIES_JSON = "stage-abilities.json";

	private final List<String> paths = new ArrayList<>();
	private final ConcurrentHashMap<Integer, List<FoeTemplate>> idFoeTemplateMap = new ConcurrentHashMap<>();
	private final ConcurrentHashMap<Integer, List<MoveTemplate>> idMoveTemplateMap = new ConcurrentHashMap<>();
	private final ConcurrentHashMap<Integer, List<Ability>> idAbilityMap = new ConcurrentHashMap<>();

	public void load() {
		loadFoes();
		loadMoves();
		loadAbilities();
	}

	public synchronized void loadFoes() {
		if (paths.contains(RES_PATH_FOES_JSON)) {
			return;
		}
		ClassLoader loader = getClass().getClassLoader();
		final InputStream foesJsonStream = loader.getResourceAsStream(RES_PATH_FOES_JSON);
		final List<FoeTemplate> foeTemplates = FoesJsonLoader.loadFromInputStream(foesJsonStream);
		saveFoes(RES_PATH_FOES_JSON, foeTemplates);
	}

	private void saveFoes(String path, List<FoeTemplate> foeTemplates) {
		paths.add(RES_PATH_FOES_JSON);
		for (FoeTemplate template : foeTemplates) {
			int id = template.id;
			idFoeTemplateMap.computeIfAbsent(id, id0 -> new ArrayList<>());
			idFoeTemplateMap.get(id).add(template);
		}
	}

	public void loadMoves() {
		loadMovesInResource(RES_PATH_MOVES_JSON);
		loadMovesInResource(RES_PATH_STAGE_MOVES_JSON);
	}

	public synchronized void loadMovesInResource(String resourcePath) {
		Objects.requireNonNull(resourcePath);
		if (paths.contains(resourcePath)) {
			return;
		}

		ClassLoader loader = getClass().getClassLoader();
		final InputStream jsonStream = loader.getResourceAsStream(resourcePath);
		Objects.requireNonNull(jsonStream,
				() -> String.format("failed to find moves resource with path: %s", resourcePath));

		final List<MoveTemplate> foeTemplates = MovesJsonLoader.loadFromInputStream(jsonStream);
		saveMoves(resourcePath, foeTemplates);
	}

	private void saveMoves(String path, List<MoveTemplate> moveTemplates) {
		paths.add(path);
		for (MoveTemplate template : moveTemplates) {
			int id = template.id;
			idMoveTemplateMap.computeIfAbsent(id, id0 -> new ArrayList<>());
			idMoveTemplateMap.get(id).add(template);
		}
	}

	public void loadAbilities() {
		loadAbilitiesInResource(RES_PATH_ABILITIES_JSON);
		loadAbilitiesInResource(RES_PATH_STAGE_ABILITIES_JSON);
	}

	public synchronized void loadAbilitiesInResource(String resourcePath) {
		Objects.requireNonNull(resourcePath);
		if (paths.contains(resourcePath)) {
			return;
		}

		ClassLoader loader = getClass().getClassLoader();
		final InputStream jsonStream = loader.getResourceAsStream(resourcePath);
		Objects.requireNonNull(jsonStream,
				() -> String.format("failed to find abilities resource with path: %s", resourcePath));

		final List<Ability> templates = AbilitiesJsonLoader.loadFromInputStream(jsonStream);
		saveAbilities(resourcePath, templates);
	}

	private void saveAbilities(String path, List<Ability> abilities) {
		paths.add(path);
		for (Ability ability : abilities) {
			int id = ability.id;
			idAbilityMap.computeIfAbsent(id, id0 -> new ArrayList<>());
			idAbilityMap.get(id).add(ability);
		}
	}

	public FoeTemplate findFoeById(int id) {
		return findFoeById(id, 0);
	}

	public FoeTemplate findFoeById(int id, int subId) {
		final List<FoeTemplate> templates = idFoeTemplateMap.get(id);
		if (templates == null) {
			return null;
		}
		return templates.stream()
				.filter(template -> template.subId == subId)
				.findFirst()
				.orElse(null);
	}

	public MoveTemplate findMoveById(int id, int level) {
		final List<MoveTemplate> templates = idMoveTemplateMap.get(id);
		if (templates == null) {
			return null;
		}
		return templates.stream()
				.filter(template -> template.level == level)
				.findFirst()
				.orElse(null);
	}

	public List<MoveTemplate> findAllMoves() {
		return idMoveTemplateMap.values().stream()
				.flatMap(List::stream)
				.collect(Collectors.toList());
	}

	public Ability findAbilityById(int id, int level) {
		final List<Ability> templates = idAbilityMap.get(id);
		if (templates == null) {
			return null;
		}
		return templates.stream()
				.filter(template -> template.level == level)
				.findFirst()
				.orElse(null);
	}
}
